Match plasma & serum samples
python3python3 python3python3 import pandas as pd samples = pd.read_csv(‘data/sample_sheet.csv’) # must be Healthy Control Study and must pass MiSeq QC samples = samples.loc[(samples[‘Study’] == ‘Healthy Controls’) & (samples[‘MISEQ.QC.PASS’] == ‘PASS’)]
samples = samples.set_index(‘MT.Unique.ID’).sort_values(by=[‘Participant.ID’, ‘Source’]) # capitalize & strip whitespace for consistency for column in [‘Gender’, ‘Race’, ‘Source’]: samples[column] = samples[column].str.capitalize() samples[column] = samples[column].str.strip() # use correct ontology terms race_ontology = {‘Asian’: ‘Asian’, ‘Black or african american’: ‘African_American’, ‘Mixed/asian & white’: ‘Multiracial’, ‘Mixed/asian &black’: ‘Multiracial’, ‘Mixed/black, white, asian’: ‘Multiracial’, ‘Native hawiian or other pacific islander’: ‘Pacific_Islander’, ‘Pacific islander’: ‘Pacific_Islander’, ‘White’: ‘White’} for id in samples.index: race = samples.at[id, ‘Race’] samples.at[id, ‘Race’] = race_ontology[race] if race in race_ontology else ‘Multiracial’ # get matched plasma & serum samples mir_counts = pd.read_csv(/get_canonical/canon_mir_counts.csv, index_col=0) samples.index = pd.Index([‘X’ + str(row) for row in samples.index]) serum_part_ids = set(samples.loc[samples[‘Source’] ==‘Serum’][‘Participant.ID’]) matched_samples = samples.loc[samples[‘Participant.ID’].isin(serum_part_ids)] matched_mir_counts = mir_counts[matched_samples.index] matched_samples.to_csv(‘data/matched_plasma-serum_samples.csv’) matched_mir_counts.to_csv(‘data/matched_plasma-serum_mir_counts.csv’)
Load the sample data and miRNA counts
rr rr samples <- read.csv(‘data/matched_plasma-serum_samples.csv’) samples <- subset(samples, Library.Generation.Set !=  ) # exclude SetRecheck samples # sample X11 is outlier – remove it and its match outlier_id <- samples[samples$X==11,]\(Participant.ID samples <- samples[samples\)Participant.ID != outlier_id,] counts <- read.csv(‘data/matched_plasma-serum_mir_counts.csv’) samples\(Participant.ID <- factor(samples\)Participant.ID) # ID is categorical, not numerical samples\(Sample.ID <- factor(samples\)Sample.ID) rownames(counts) = counts\(X rownames(samples) = samples\)X counts$X <- NULL # remove extra column counts <- counts[,rownames(samples)] head(samples) r head(counts)
Filter
rr rr library(edgeR) design <- model.matrix(~Participant.ID + Source, samples) dge = DGEList(counts = counts, samples = samples) # require miRNAs to have CPM > 1 in at least 2 samples countsPerMillion <- edgeR::cpm(dge) countCheck <- countsPerMillion > 1 head(countCheck)
X1 X107 X2 X108 X14 X110 X15 X18 X21 X113 X22 X114 X26 X115 X40 X116 X46 X119 X48 X120 X52
hsa-let-7a-3p TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE FALSE TRUE TRUE TRUE FALSE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
hsa-let-7a-5p TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
hsa-let-7b-3p TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE FALSE TRUE TRUE TRUE
hsa-let-7b-5p TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
hsa-let-7c-3p FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
hsa-let-7c-5p TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
X121 X56 X122 X59 X123 X60 X124 X62 X125 X64 X126 X66 X127 X67 X128 X68 X129 X69 X130 X131 X77
hsa-let-7a-3p TRUE TRUE FALSE TRUE FALSE TRUE FALSE TRUE FALSE TRUE TRUE TRUE TRUE TRUE FALSE TRUE FALSE TRUE FALSE TRUE TRUE
hsa-let-7a-5p TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
hsa-let-7b-3p TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
hsa-let-7b-5p TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
hsa-let-7c-3p FALSE FALSE FALSE FALSE FALSE FALSE FALSE TRUE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
hsa-let-7c-5p TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
X132 X133 X88 X134 X89 X135 X90 X136 X93 X137 X138
hsa-let-7a-3p FALSE FALSE TRUE TRUE TRUE FALSE TRUE TRUE TRUE TRUE TRUE
hsa-let-7a-5p TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
hsa-let-7b-3p TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
hsa-let-7b-5p TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
hsa-let-7c-3p FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE TRUE FALSE FALSE
hsa-let-7c-5p TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
rr rr keep <- which(rowSums(countCheck) >= 2) dge <- dge[keep,]
Explore variance
rr rr library(SingleCellExperiment) library(scater) reads_sce <- SingleCellExperiment(assays=list(counts=dge\(counts), colData=dge\)samples) # remove unexpressed miRNAs keep_feature <- rowSums(counts(reads_sce) > 0) > 0 reads_sce <- reads_sce[keep_feature, ] reads_sce <- calculateQCMetrics(reads_sce)
Note that the names of some metrics have changed, see 'Renamed metrics' in ?calculateQCMetrics.
Old names are currently maintained for back-compatibility, but may be removed in future releases.
rr rr # create separate plasma & serum SCEs plasma_samples <- subset(colData(reads_sce), Source == ) plasma_counts <- as.data.frame(counts(reads_sce))[rownames(plasma_samples)] plasma_sce <- SingleCellExperiment(assays=list(counts=as.matrix(plasma_counts)), colData=plasma_samples) plasma_sce <- calculateQCMetrics(plasma_sce)
Note that the names of some metrics have changed, see 'Renamed metrics' in ?calculateQCMetrics.
Old names are currently maintained for back-compatibility, but may be removed in future releases.
rr rr serum_samples <- subset(colData(reads_sce), Source == ) serum_counts <- as.data.frame(counts(reads_sce))[rownames(serum_samples)] serum_sce <- SingleCellExperiment(assays=list(counts=as.matrix(serum_counts)), colData=serum_samples) serum_sce <- calculateQCMetrics(serum_sce)
Note that the names of some metrics have changed, see 'Renamed metrics' in ?calculateQCMetrics.
Old names are currently maintained for back-compatibility, but may be removed in future releases.
rr rr # log transform cpm(reads_sce) <- calculateCPM(reads_sce) reads_sce <- normalize(reads_sce)
using library sizes as size factors
rr rr logcounts(reads_sce) <- log2(calculateCPM(reads_sce) + 1)
Examine sources of variation
rr rr plotPCA(reads_sce, exprs_values = , colour_by = .Generation.Set, size_by = _features, shape_by = )
non-plotting arguments like 'exprs_values' should go in 'run_args'

rr rr for (var in c(_features, , .Generation.Set, , .ID, , , , .Date)) { print( plotQC(reads_sce, type = -pcs, exprs_values = , variable = var) ) + ggtitle(var) }









Normalize & Remove unwanted sources of variation
rr rr library(RUVSeq) library(ggplot2) library(mvoutlier)
Loading required package: sgeostat
sROC 0.1-2 loaded
rr rr dge <- calcNormFactors(dge) dge <- estimateGLMCommonDisp(dge, design) dge <- estimateGLMTagwiseDisp(dge, design) fit <- glmFit(dge, design) res <- residuals(fit, type=) set <- newSeqExpressionSet(dge\(counts, phenoData=dge\)samples) ruvr_sets <- list() for(k in 1:5) { ruvr_sets[[k]] <- RUVr(set, row.names(dge), k=k, res) assay(reads_sce, paste(k=, toString(k))) <- log2(t(t(assayData(ruvr_sets[[k]])\(normalizedCounts) / colSums(assayData(ruvr_sets[[k]])\)normalizedCounts) * 1e6) + 1) } for(n in assayNames(reads_sce)) { print( plotPCA( reads_sce, colour_by = .Generation.Set, size_by = _features, shape_by = , exprs_values = n ) + ggtitle(n) ) }
non-plotting arguments like 'exprs_values' should go in 'run_args'








rr rr for(k in 1:5) { ruvr_sets[[k]]\(set <- RUVr(set, row.names(dge), k=k, res) assay(reads_sce, paste(\RUVr k=\, toString(k))) <- log2(t(t(assayData(ruvr_sets[[k]]\)set)\(normalizedCounts) / colSums(assayData(ruvr_sets[[k]]\)set)$normalizedCounts) * 1e6) + 1) }
Error in `[[<-.data.frame`(`*tmp*`, i, value = new(\SeqExpressionSet\, :
replacement has 989 rows, data has 53
Detect outliers
rr rr reads_sce <- runPCA(reads_sce, use_coldata = TRUE, detect_outliers = TRUE)
failed to find 'pct_counts_feature_control' in column metadatafailed to find 'total_features_by_counts_feature_control' in column metadatafailed to find 'log10_total_counts_endogenous' in column metadatafailed to find 'log10_total_counts_feature_control' in column metadata
rr rr outliers <- colnames(reads_sce)[reads_sce$outlier] head(outliers)
character(0)
Examine sources of variance after removing unwanted variation
rr rr for (var in c(_features, , .Generation.Set, , .ID, , , , .Date)) { print( plotQC(reads_sce, type = -pcs, exprs_values = 1, variable = var) ) + ggtitle(var) }
Error in assay(object, exprs_values) :
'assay(<SingleCellExperiment>, i=\character\, ...)' invalid subscript 'i'
'RUVr1' not in names(assays(<SingleCellExperiment>))
rr rr for (var in c(_features, , .Generation.Set, , .ID, , , , .Date)) { print( plotQC(reads_sce, type = -pcs, exprs_values = k= 2, variable = var) ) + ggtitle(var) }









Visualize top highly-expressed plasma-vs-serum miRNAs
rr rr library(RColorBrewer) library(reshape2) # Ryan’s code, modified norm.expr.matr <- exprs(reads_sce) # Rank the mean expression values for plasma/serum miRs. Highest expression = 1 mean.expr.plasma.rank <- rank(-1*rowMeans(norm.expr.matr[, colData(reads_sce)\(Source==\Plasma\])) mean.expr.serum.rank <- rank(-1*rowMeans(norm.expr.matr[, colData(reads_sce)\)Source==])) top_N <- 18 # top_N=18 resuls in 20 miRNAs in the plot top.miRs <- row.names(norm.expr.matr)[mean.expr.plasma.rank <= top_N | mean.expr.serum.rank <= top_N] # get the names of the top miRs in plasma or serum norm.expr.top <- norm.expr.matr[top.miRs, ] # Get the expression matrix for the top mIRs norm.expr.melt <- reshape2::melt(norm.expr.top) # Convert your normalized expression matrix to a 3 column data.frame (row.name, col.name, expression value). Melt is in the dpylr package, I believe. colnames(norm.expr.melt) <- c(.ID, .Unique.ID, .expr) # just so it’s easier for me to tell you which columns I’m using. norm.expr.melt\(Source <- \\ for (row_num in 1:nrow(norm.expr.melt)){ # Pull the plasma/serum source value from the column metadata. mt_unique_id <- norm.expr.melt[row_num, ]\)MT.Unique.ID norm.expr.melt[row_num, ] <- as.character(samples[mt_unique_id,]) } # This would be a simple boxplot with plasma/serum side-by-side. Overlaying the boxes over points takes a little more tweaking to get the dodge/width right, but it’s doable. ggplot(norm.expr.melt, aes(x=reorder(miR.ID, norm.expr, FUN=median), y=norm.expr, fill=Source)) + geom_boxplot(pos=, outlier.size=0.5) + ggtitle(20 Expressed miRNAs) + ylab(Expression) + xlab(ID) + theme(panel.grid.major.x = element_line(size = 0.5, linetype = ‘solid’, colour = ), panel.grid.major.y = element_blank(), axis.text.x = element_text(angle = 90, hjust = 1))

rr rr ggplot(norm.expr.melt, aes(x=reorder(miR.ID, norm.expr, FUN=median), y=norm.expr, fill=Source)) + geom_boxplot(pos=, outlier.size=0.5) + ggtitle(20 Expressed miRNAs) + ylab(Expression) + xlab(ID) + theme(panel.grid.major.y = element_line(size = 0.5, linetype = ‘solid’, colour = ), panel.grid.major.x = element_blank()) + coord_flip()

DE Analysis with DESeq2
rr rr library(DESeq2) # TODO: use filtered & RUVSeq-corrected data deseq2Data <- DESeqDataSetFromMatrix(countData = dge\(counts, design = ~ Participant.ID + Source, colData = dge\)samples) deseq2Data <- DESeq(deseq2Data)
estimating size factors
estimating dispersions
gene-wise dispersion estimates
mean-dispersion relationship
final dispersion estimates
fitting model and testing
rr rr results <- results(deseq2Data, cooksCutoff=FALSE, independentFiltering=FALSE) head(as.data.frame(results))
Create a heatmap of sample-to-sample distances
rr rr sampleDists <- dist(t(assay(vstData))) # compute sample-to-sample distances sampleDistMatrix <- as.matrix(sampleDists) rownames(sampleDistMatrix) <- paste(vstData\(Participant.ID, vstData\)Source, sep=Â - ) colnames(sampleDistMatrix) <- NULL colors <- colorRampPalette( rev(brewer.pal(9, )) )(255) pheatmap(sampleDistMatrix, clustering_distance_rows=sampleDists, clustering_distance_cols=sampleDists, col=colors)
Create a PCA plot showing Age x Gender
rr rr plotPCA( reads_sce, colour_by = , shape_by = , size_by = _features, exprs_values = k= 2
) + ggtitle(-Normalized Expression (k=2))
non-plotting arguments like 'exprs_values' should go in 'run_args'

save.image("diff_expr_matched_plasma_vs_serum.RData")
LS0tCnRpdGxlOiAibWlSTkEgRGlmZmVyZW50aWFsIGV4cHJlc3Npb24gYW5hbHlzaXMgZm9yIG1hdGNoZWQgcGxhc21hICYgc2VydW0gc2FtcGxlcyIKb3V0cHV0OiBodG1sX25vdGVib29rCmVkaXRvcl9vcHRpb25zOiAKICBjaHVua19vdXRwdXRfdHlwZTogaW5saW5lCi0tLQojIE1hdGNoIHBsYXNtYSAmIHNlcnVtIHNhbXBsZXMKYGBge3B5dGhvbjN9CmltcG9ydCBwYW5kYXMgYXMgcGQKc2FtcGxlcyA9IHBkLnJlYWRfY3N2KCdkYXRhL3NhbXBsZV9zaGVldC5jc3YnKQojIG11c3QgYmUgSGVhbHRoeSBDb250cm9sIFN0dWR5IGFuZCBtdXN0IHBhc3MgTWlTZXEgUUMKc2FtcGxlcyA9IHNhbXBsZXMubG9jWyhzYW1wbGVzWydTdHVkeSddID09ICdIZWFsdGh5IENvbnRyb2xzJykgJiAoc2FtcGxlc1snTUlTRVEuUUMuUEFTUyddID09ICdQQVNTJyldICAKc2FtcGxlcyA9IHNhbXBsZXMuc2V0X2luZGV4KCdNVC5VbmlxdWUuSUQnKS5zb3J0X3ZhbHVlcyhieT1bJ1BhcnRpY2lwYW50LklEJywgJ1NvdXJjZSddKQojIGNhcGl0YWxpemUgJiBzdHJpcCB3aGl0ZXNwYWNlIGZvciBjb25zaXN0ZW5jeQpmb3IgY29sdW1uIGluIFsnR2VuZGVyJywgJ1JhY2UnLCAnU291cmNlJ106CiAgICBzYW1wbGVzW2NvbHVtbl0gPSBzYW1wbGVzW2NvbHVtbl0uc3RyLmNhcGl0YWxpemUoKQogICAgc2FtcGxlc1tjb2x1bW5dID0gc2FtcGxlc1tjb2x1bW5dLnN0ci5zdHJpcCgpCiMgdXNlIGNvcnJlY3Qgb250b2xvZ3kgdGVybXMKcmFjZV9vbnRvbG9neSA9IHsnQXNpYW4nOiAnQXNpYW4nLAogJ0JsYWNrIG9yIGFmcmljYW4gYW1lcmljYW4nOiAnQWZyaWNhbl9BbWVyaWNhbicsCiAnTWl4ZWQvYXNpYW4gJiB3aGl0ZSc6ICdNdWx0aXJhY2lhbCcsCiAnTWl4ZWQvYXNpYW4gJmJsYWNrJzogJ011bHRpcmFjaWFsJywKICdNaXhlZC9ibGFjaywgd2hpdGUsIGFzaWFuJzogJ011bHRpcmFjaWFsJywKICdOYXRpdmUgaGF3aWlhbiBvciBvdGhlciBwYWNpZmljIGlzbGFuZGVyJzogJ1BhY2lmaWNfSXNsYW5kZXInLAogJ1BhY2lmaWMgaXNsYW5kZXInOiAnUGFjaWZpY19Jc2xhbmRlcicsCiAnV2hpdGUnOiAnV2hpdGUnfQpmb3IgaWQgaW4gc2FtcGxlcy5pbmRleDoKICAgIHJhY2UgPSBzYW1wbGVzLmF0W2lkLCAnUmFjZSddCiAgICBzYW1wbGVzLmF0W2lkLCAnUmFjZSddID0gcmFjZV9vbnRvbG9neVtyYWNlXSBpZiByYWNlIGluIHJhY2Vfb250b2xvZ3kgZWxzZSAnTXVsdGlyYWNpYWwnCiMgZ2V0IG1hdGNoZWQgcGxhc21hICYgc2VydW0gc2FtcGxlcwptaXJfY291bnRzID0gcGQucmVhZF9jc3YoImRhdGEvZ2V0X2Nhbm9uaWNhbC9jYW5vbl9taXJfY291bnRzLmNzdiIsIGluZGV4X2NvbD0wKQpzYW1wbGVzLmluZGV4ID0gcGQuSW5kZXgoWydYJyArIHN0cihyb3cpIGZvciByb3cgaW4gc2FtcGxlcy5pbmRleF0pCnNlcnVtX3BhcnRfaWRzID0gc2V0KHNhbXBsZXMubG9jW3NhbXBsZXNbJ1NvdXJjZSddID09J1NlcnVtJ11bJ1BhcnRpY2lwYW50LklEJ10pCm1hdGNoZWRfc2FtcGxlcyA9IHNhbXBsZXMubG9jW3NhbXBsZXNbJ1BhcnRpY2lwYW50LklEJ10uaXNpbihzZXJ1bV9wYXJ0X2lkcyldCm1hdGNoZWRfbWlyX2NvdW50cyA9IG1pcl9jb3VudHNbbWF0Y2hlZF9zYW1wbGVzLmluZGV4XQptYXRjaGVkX3NhbXBsZXMudG9fY3N2KCdkYXRhL21hdGNoZWRfcGxhc21hLXNlcnVtX3NhbXBsZXMuY3N2JykKbWF0Y2hlZF9taXJfY291bnRzLnRvX2NzdignZGF0YS9tYXRjaGVkX3BsYXNtYS1zZXJ1bV9taXJfY291bnRzLmNzdicpCmBgYAojIyBMb2FkIHRoZSBzYW1wbGUgZGF0YSBhbmQgbWlSTkEgY291bnRzCmBgYHtyfQpzYW1wbGVzIDwtIHJlYWQuY3N2KCdkYXRhL21hdGNoZWRfcGxhc21hLXNlcnVtX3NhbXBsZXMuY3N2JykKc2FtcGxlcyA8LSBzdWJzZXQoc2FtcGxlcywgTGlicmFyeS5HZW5lcmF0aW9uLlNldCAhPSAiU2V0UmVjaGVjayIgKSAgIyBleGNsdWRlIFNldFJlY2hlY2sgc2FtcGxlcwojIHNhbXBsZSBYMTEgaXMgb3V0bGllciAtLSByZW1vdmUgaXQgYW5kIGl0cyBtYXRjaApvdXRsaWVyX2lkIDwtIHNhbXBsZXNbc2FtcGxlcyRYPT0iWDExIixdJFBhcnRpY2lwYW50LklECnNhbXBsZXMgPC0gc2FtcGxlc1tzYW1wbGVzJFBhcnRpY2lwYW50LklEICE9IG91dGxpZXJfaWQsXQpjb3VudHMgPC0gcmVhZC5jc3YoJ2RhdGEvbWF0Y2hlZF9wbGFzbWEtc2VydW1fbWlyX2NvdW50cy5jc3YnKQpzYW1wbGVzJFBhcnRpY2lwYW50LklEIDwtIGZhY3RvcihzYW1wbGVzJFBhcnRpY2lwYW50LklEKSAgIyBJRCBpcyBjYXRlZ29yaWNhbCwgbm90IG51bWVyaWNhbApzYW1wbGVzJFNhbXBsZS5JRCA8LSBmYWN0b3Ioc2FtcGxlcyRTYW1wbGUuSUQpCnJvd25hbWVzKGNvdW50cykgPSBjb3VudHMkWApyb3duYW1lcyhzYW1wbGVzKSA9IHNhbXBsZXMkWApjb3VudHMkWCA8LSBOVUxMICAjIHJlbW92ZSBleHRyYSBjb2x1bW4KY291bnRzIDwtIGNvdW50c1sscm93bmFtZXMoc2FtcGxlcyldCmhlYWQoc2FtcGxlcykKaGVhZChjb3VudHMpCmBgYAojIyBGaWx0ZXIKYGBge3J9CmxpYnJhcnkoZWRnZVIpCmRlc2lnbiA8LSBtb2RlbC5tYXRyaXgoflBhcnRpY2lwYW50LklEICsgU291cmNlLCBzYW1wbGVzKQpkZ2UgPSBER0VMaXN0KGNvdW50cyA9IGNvdW50cywgc2FtcGxlcyA9IHNhbXBsZXMpCiMgcmVxdWlyZSBtaVJOQXMgdG8gaGF2ZSBDUE0gPiAxIGluIGF0IGxlYXN0IDIgc2FtcGxlcwpjb3VudHNQZXJNaWxsaW9uIDwtIGVkZ2VSOjpjcG0oZGdlKQpjb3VudENoZWNrIDwtIGNvdW50c1Blck1pbGxpb24gPiAxCmhlYWQoY291bnRDaGVjaykKa2VlcCA8LSB3aGljaChyb3dTdW1zKGNvdW50Q2hlY2spID49IDIpIApkZ2UgPC0gZGdlW2tlZXAsXQpgYGAKIyMgRXhwbG9yZSB2YXJpYW5jZQpgYGB7cn0KbGlicmFyeShTaW5nbGVDZWxsRXhwZXJpbWVudCkKbGlicmFyeShzY2F0ZXIpCnJlYWRzX3NjZSA8LSBTaW5nbGVDZWxsRXhwZXJpbWVudChhc3NheXM9bGlzdChjb3VudHM9ZGdlJGNvdW50cyksICBjb2xEYXRhPWRnZSRzYW1wbGVzKQojIHJlbW92ZSB1bmV4cHJlc3NlZCBtaVJOQXMKa2VlcF9mZWF0dXJlIDwtIHJvd1N1bXMoY291bnRzKHJlYWRzX3NjZSkgPiAwKSA+IDAKcmVhZHNfc2NlIDwtIHJlYWRzX3NjZVtrZWVwX2ZlYXR1cmUsIF0KcmVhZHNfc2NlIDwtIGNhbGN1bGF0ZVFDTWV0cmljcyhyZWFkc19zY2UpCiMgY3JlYXRlIHNlcGFyYXRlIHBsYXNtYSAmIHNlcnVtIFNDRXMKcGxhc21hX3NhbXBsZXMgPC0gc3Vic2V0KGNvbERhdGEocmVhZHNfc2NlKSwgU291cmNlID09ICJQbGFzbWEiKQpwbGFzbWFfY291bnRzIDwtIGFzLmRhdGEuZnJhbWUoY291bnRzKHJlYWRzX3NjZSkpW3Jvd25hbWVzKHBsYXNtYV9zYW1wbGVzKV0KcGxhc21hX3NjZSA8LSBTaW5nbGVDZWxsRXhwZXJpbWVudChhc3NheXM9bGlzdChjb3VudHM9YXMubWF0cml4KHBsYXNtYV9jb3VudHMpKSwgY29sRGF0YT1wbGFzbWFfc2FtcGxlcykKcGxhc21hX3NjZSA8LSBjYWxjdWxhdGVRQ01ldHJpY3MocGxhc21hX3NjZSkKc2VydW1fc2FtcGxlcyA8LSBzdWJzZXQoY29sRGF0YShyZWFkc19zY2UpLCBTb3VyY2UgPT0gIlNlcnVtIikKc2VydW1fY291bnRzIDwtIGFzLmRhdGEuZnJhbWUoY291bnRzKHJlYWRzX3NjZSkpW3Jvd25hbWVzKHNlcnVtX3NhbXBsZXMpXQpzZXJ1bV9zY2UgPC0gU2luZ2xlQ2VsbEV4cGVyaW1lbnQoYXNzYXlzPWxpc3QoY291bnRzPWFzLm1hdHJpeChzZXJ1bV9jb3VudHMpKSwgY29sRGF0YT1zZXJ1bV9zYW1wbGVzKQpzZXJ1bV9zY2UgPC0gY2FsY3VsYXRlUUNNZXRyaWNzKHNlcnVtX3NjZSkKIyBsb2cgdHJhbnNmb3JtCmNwbShyZWFkc19zY2UpIDwtIGNhbGN1bGF0ZUNQTShyZWFkc19zY2UpCnJlYWRzX3NjZSA8LSBub3JtYWxpemUocmVhZHNfc2NlKQpsb2djb3VudHMocmVhZHNfc2NlKSA8LSBsb2cyKGNhbGN1bGF0ZUNQTShyZWFkc19zY2UpICsgMSkKIyB2aXN1YWxpemUKaGlzdChyZWFkc19zY2UkdG90YWxfY291bnRzLCBicmVha3M9MTAwKSAgIyBjb3VudHMgcGVyIHNhbXBsZQpoaXN0KHJlYWRzX3NjZSR0b3RhbF9mZWF0dXJlcywgYnJlYWtzPTEwMCkgICMgY291bnRzIHBlciBtaVJOQQpwbG90UUMocmVhZHNfc2NlLCB0eXBlID0gImhpZ2hlc3QtZXhwcmVzc2lvbiIpICsgZ2d0aXRsZSgiUGxhc21hICYgU2VydW0iKQpwbG90UUMocGxhc21hX3NjZSwgdHlwZSA9ICJoaWdoZXN0LWV4cHJlc3Npb24iKSArIGdndGl0bGUoIlBsYXNtYSIpCnBsb3RRQyhzZXJ1bV9zY2UsIHR5cGUgPSAiaGlnaGVzdC1leHByZXNzaW9uIikgKyBnZ3RpdGxlKCJTZXJ1bSIpCnBsb3RRQyhyZWFkc19zY2UsIHR5cGU9ImV4cGxhbmF0b3J5LXZhcmlhYmxlcyIsIHZhcmlhYmxlcz1jKCJJbmRleCIsICJQYXJ0aWNpcGFudC5JRCIsICJDb2xsZWN0aW9uLkRhdGUiLCAiTGlicmFyeS5HZW5lcmF0aW9uLlNldCIsICJNaVNlcS5RQy5SdW4iLCAiU291cmNlIiwgJ3RvdGFsX2ZlYXR1cmVzJywgIkFnZSIsICJSYWNlIiwgIkdlbmRlciIpKQpgYGAKIyMgRXhhbWluZSBzb3VyY2VzIG9mIHZhcmlhdGlvbgpgYGB7cn0KcGxvdFBDQShyZWFkc19zY2UsIGV4cHJzX3ZhbHVlcyA9ICJsb2djb3VudHMiLCBjb2xvdXJfYnkgPSAiTGlicmFyeS5HZW5lcmF0aW9uLlNldCIsIHNpemVfYnkgPSAidG90YWxfZmVhdHVyZXMiLCBzaGFwZV9ieSA9ICJTb3VyY2UiKQpmb3IgKHZhciBpbiBjKCJ0b3RhbF9mZWF0dXJlcyIsICJBZ2UiLCAiTGlicmFyeS5HZW5lcmF0aW9uLlNldCIsICJJbmRleCIsICJQYXJ0aWNpcGFudC5JRCIsICJTb3VyY2UiLCAiUmFjZSIsICJHZW5kZXIiLCAiQ29sbGVjdGlvbi5EYXRlIikpIHsKICBwcmludCgKICAgIHBsb3RRQyhyZWFkc19zY2UsIHR5cGUgPSAiZmluZC1wY3MiLCBleHByc192YWx1ZXMgPSAibG9nY291bnRzIiwgdmFyaWFibGUgPSB2YXIpCiAgICApICArIGdndGl0bGUodmFyKSAKICB9CmBgYAoKIyMgTm9ybWFsaXplICYgUmVtb3ZlIHVud2FudGVkIHNvdXJjZXMgb2YgdmFyaWF0aW9uCmBgYHtyfQpsaWJyYXJ5KFJVVlNlcSkKbGlicmFyeShnZ3Bsb3QyKQpsaWJyYXJ5KG12b3V0bGllcikKZGdlIDwtIGNhbGNOb3JtRmFjdG9ycyhkZ2UpCmRnZSA8LSBlc3RpbWF0ZUdMTUNvbW1vbkRpc3AoZGdlLCBkZXNpZ24pCmRnZSA8LSBlc3RpbWF0ZUdMTVRhZ3dpc2VEaXNwKGRnZSwgZGVzaWduKQpmaXQgPC0gZ2xtRml0KGRnZSwgZGVzaWduKQpyZXMgPC0gcmVzaWR1YWxzKGZpdCwgdHlwZT0iZGV2aWFuY2UiKQpzZXQgPC0gbmV3U2VxRXhwcmVzc2lvblNldChkZ2UkY291bnRzLCBwaGVub0RhdGE9ZGdlJHNhbXBsZXMpCnJ1dnJfc2V0cyA8LSBsaXN0KCkKZm9yKGsgaW4gMTo1KSB7CiAgcnV2cl9zZXRzW1trXV0gPC0gUlVWcihzZXQsIHJvdy5uYW1lcyhkZ2UpLCBrPWssIHJlcykKICBhc3NheShyZWFkc19zY2UsIHBhc3RlKCJSVVZyIGs9IiwgdG9TdHJpbmcoaykpKSA8LSBsb2cyKHQodChhc3NheURhdGEocnV2cl9zZXRzW1trXV0pJG5vcm1hbGl6ZWRDb3VudHMpIC8gY29sU3Vtcyhhc3NheURhdGEocnV2cl9zZXRzW1trXV0pJG5vcm1hbGl6ZWRDb3VudHMpICogMWU2KSArIDEpCn0KZm9yKG4gaW4gYXNzYXlOYW1lcyhyZWFkc19zY2UpKSB7CiAgcHJpbnQoCiAgICAgICAgcGxvdFBDQSgKICAgICAgICAgICAgcmVhZHNfc2NlLAogICAgICAgICAgICBjb2xvdXJfYnkgPSAiTGlicmFyeS5HZW5lcmF0aW9uLlNldCIsCiAgICAgICAgICAgIHNpemVfYnkgPSAidG90YWxfZmVhdHVyZXMiLAogICAgICAgICAgICBzaGFwZV9ieSA9ICJTb3VyY2UiLAogICAgICAgICAgICBleHByc192YWx1ZXMgPSBuCiAgICAgICAgKSArIGdndGl0bGUobikKICApCn0KYGBgCiMjIERldGVjdCBvdXRsaWVycwpgYGB7cn0KcmVhZHNfc2NlIDwtIHJ1blBDQShyZWFkc19zY2UsIHVzZV9jb2xkYXRhID0gVFJVRSwgZGV0ZWN0X291dGxpZXJzID0gVFJVRSkKb3V0bGllcnMgPC0gY29sbmFtZXMocmVhZHNfc2NlKVtyZWFkc19zY2Ukb3V0bGllcl0KaGVhZChvdXRsaWVycykKYGBgCiMjIEV4YW1pbmUgc291cmNlcyBvZiB2YXJpYW5jZSBhZnRlciByZW1vdmluZyB1bndhbnRlZCB2YXJpYXRpb24KYGBge3J9CmZvciAodmFyIGluIGMoInRvdGFsX2ZlYXR1cmVzIiwgIkFnZSIsICJMaWJyYXJ5LkdlbmVyYXRpb24uU2V0IiwgIkluZGV4IiwgIlBhcnRpY2lwYW50LklEIiwgIlNvdXJjZSIsICJSYWNlIiwgIkdlbmRlciIsICJDb2xsZWN0aW9uLkRhdGUiKSkgewogIHByaW50KAogICAgcGxvdFFDKHJlYWRzX3NjZSwgdHlwZSA9ICJmaW5kLXBjcyIsIGV4cHJzX3ZhbHVlcyA9ICJSVVZyIGs9IDIiLCB2YXJpYWJsZSA9IHZhcikKICAgICkgICsgZ2d0aXRsZSh2YXIpIAp9CmBgYAojIyBWaXN1YWxpemUgdG9wIGhpZ2hseS1leHByZXNzZWQgcGxhc21hLXZzLXNlcnVtIG1pUk5BcwpgYGB7cn0KbGlicmFyeShSQ29sb3JCcmV3ZXIpCmxpYnJhcnkocmVzaGFwZTIpCiMgUnlhbidzIGNvZGUsIG1vZGlmaWVkCm5vcm0uZXhwci5tYXRyIDwtIGV4cHJzKHJlYWRzX3NjZSkKIyBSYW5rIHRoZSBtZWFuIGV4cHJlc3Npb24gdmFsdWVzIGZvciBwbGFzbWEvc2VydW0gbWlScy4gSGlnaGVzdCBleHByZXNzaW9uID0gMQptZWFuLmV4cHIucGxhc21hLnJhbmsgPC0gcmFuaygtMSpyb3dNZWFucyhub3JtLmV4cHIubWF0clssIGNvbERhdGEocmVhZHNfc2NlKSRTb3VyY2U9PSJQbGFzbWEiXSkpCm1lYW4uZXhwci5zZXJ1bS5yYW5rIDwtIHJhbmsoLTEqcm93TWVhbnMobm9ybS5leHByLm1hdHJbLCBjb2xEYXRhKHJlYWRzX3NjZSkkU291cmNlPT0iU2VydW0iXSkpCnRvcF9OIDwtIDE4ICAjIHRvcF9OPTE4IHJlc3VscyBpbiAyMCBtaVJOQXMgaW4gdGhlIHBsb3QKdG9wLm1pUnMgPC0gcm93Lm5hbWVzKG5vcm0uZXhwci5tYXRyKVttZWFuLmV4cHIucGxhc21hLnJhbmsgPD0gdG9wX04gfCBtZWFuLmV4cHIuc2VydW0ucmFuayA8PSB0b3BfTl0gIyBnZXQgdGhlIG5hbWVzIG9mIHRoZSB0b3AgbWlScyBpbiBwbGFzbWEgb3Igc2VydW0Kbm9ybS5leHByLnRvcCA8LSBub3JtLmV4cHIubWF0clt0b3AubWlScywgXSAjIEdldCB0aGUgZXhwcmVzc2lvbiBtYXRyaXggZm9yIHRoZSB0b3AgbUlScwpub3JtLmV4cHIubWVsdCA8LSByZXNoYXBlMjo6bWVsdChub3JtLmV4cHIudG9wKSAjIENvbnZlcnQgeW91ciBub3JtYWxpemVkIGV4cHJlc3Npb24gbWF0cml4IHRvIGEgMyBjb2x1bW4gZGF0YS5mcmFtZSAocm93Lm5hbWUsIGNvbC5uYW1lLCBleHByZXNzaW9uIHZhbHVlKS4gTWVsdCBpcyBpbiB0aGUgZHB5bHIgcGFja2FnZSwgSSBiZWxpZXZlLgpjb2xuYW1lcyhub3JtLmV4cHIubWVsdCkgPC0gYygibWlSLklEIiwgIk1ULlVuaXF1ZS5JRCIsICJub3JtLmV4cHIiKSAjIGp1c3Qgc28gaXQncyBlYXNpZXIgZm9yIG1lIHRvIHRlbGwgeW91IHdoaWNoIGNvbHVtbnMgSSdtIHVzaW5nLgpub3JtLmV4cHIubWVsdCRTb3VyY2UgPC0gIiIKZm9yIChyb3dfbnVtIGluIDE6bnJvdyhub3JtLmV4cHIubWVsdCkpeyAgIyBQdWxsIHRoZSBwbGFzbWEvc2VydW0gc291cmNlIHZhbHVlIGZyb20gdGhlIGNvbHVtbiBtZXRhZGF0YS4KICBtdF91bmlxdWVfaWQgPC0gbm9ybS5leHByLm1lbHRbcm93X251bSwgXSRNVC5VbmlxdWUuSUQKICBub3JtLmV4cHIubWVsdFtyb3dfbnVtLCAiU291cmNlIl0gPC0gYXMuY2hhcmFjdGVyKHNhbXBsZXNbbXRfdW5pcXVlX2lkLCJTb3VyY2UiXSkKfQojIFRoaXMgd291bGQgYmUgYSBzaW1wbGUgYm94cGxvdCB3aXRoIHBsYXNtYS9zZXJ1bSBzaWRlLWJ5LXNpZGUuIE92ZXJsYXlpbmcgdGhlIGJveGVzIG92ZXIgcG9pbnRzIHRha2VzIGEgbGl0dGxlIG1vcmUgdHdlYWtpbmcgdG8gZ2V0IHRoZSBkb2RnZS93aWR0aCByaWdodCwgYnV0IGl0J3MgZG9hYmxlLgpnZ3Bsb3Qobm9ybS5leHByLm1lbHQsIGFlcyh4PXJlb3JkZXIobWlSLklELCBub3JtLmV4cHIsIEZVTj1tZWRpYW4pLCB5PW5vcm0uZXhwciwgZmlsbD1Tb3VyY2UpKSArIGdlb21fYm94cGxvdChwb3M9ImRvZGdlIiwgb3V0bGllci5zaXplPTAuNSkgKyAKICBnZ3RpdGxlKCJUb3AgMjAgRXhwcmVzc2VkIG1pUk5BcyIpICsgeWxhYigiTm9ybWFsaXplZCBFeHByZXNzaW9uIikgKyB4bGFiKCJtaVJOQSBJRCIpICsgCiAgdGhlbWUocGFuZWwuZ3JpZC5tYWpvci54ID0gZWxlbWVudF9saW5lKHNpemUgPSAwLjUsIGxpbmV0eXBlID0gJ3NvbGlkJywgY29sb3VyID0gImdyZXkiKSwgCiAgICAgICAgcGFuZWwuZ3JpZC5tYWpvci55ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTAsIGhqdXN0ID0gMSkpCmdncGxvdChub3JtLmV4cHIubWVsdCwgYWVzKHg9cmVvcmRlcihtaVIuSUQsIG5vcm0uZXhwciwgRlVOPW1lZGlhbiksIHk9bm9ybS5leHByLCBmaWxsPVNvdXJjZSkpICsgZ2VvbV9ib3hwbG90KHBvcz0iZG9kZ2UiLCBvdXRsaWVyLnNpemU9MC41KSArIAogIGdndGl0bGUoIlRvcCAyMCBFeHByZXNzZWQgbWlSTkFzIikgKyB5bGFiKCJOb3JtYWxpemVkIEV4cHJlc3Npb24iKSArIHhsYWIoIm1pUk5BIElEIikgKyAKICB0aGVtZShwYW5lbC5ncmlkLm1ham9yLnkgPSBlbGVtZW50X2xpbmUoc2l6ZSA9IDAuNSwgbGluZXR5cGUgPSAnc29saWQnLCBjb2xvdXIgPSAiZ3JleSIpLCAKICAgICAgICBwYW5lbC5ncmlkLm1ham9yLnggPSBlbGVtZW50X2JsYW5rKCkpICsgY29vcmRfZmxpcCgpCmBgYAoKIyBERSBhbmFseXNpcyB3aXRoIEVkZ2VSCmBgYHtyfQpsaWJyYXJ5KHBoZWF0bWFwKQojIGJhc2VkIG9uIFJ5YW4ncyBjb2RlLCBwYXNzIFJVVlNlcS1jb3JyZWN0ZWQgZGF0YSB0byBlZGdlUgpydXZyMiA8LSBydXZyX3NldHNbWzJdXQpkZXNpZ24gPC0gbW9kZWwubWF0cml4KH4gUGFydGljaXBhbnQuSUQgKyBTb3VyY2UgKyBXXzEgKyBXXzIsIHBEYXRhKHJ1dnIyKSkKZGdlIDwtIGVzdGltYXRlRGlzcChkZ2UsIGRlc2lnbiA9IGRlc2lnbiwgdGFnd2lzZSA9IFRSVUUsIHJvYnVzdCA9IFRSVUUpCmZpdCA8LSBnbG1GaXQoZGdlLCBkZXNpZ24pCiMgY29udHJhc3Qgc291cmNlIChwbGFzbWEgdnMgc2VydW0pCmxydCA8LSBnbG1MUlQoZml0LCBjb2VmPSJTb3VyY2VTZXJ1bSIpCmFscGhhX2xldmVsIDwtIDAuMDUKcmVzdWx0cyA8LSBkYXRhLmZyYW1lKHRvcFRhZ3MobHJ0LCBuPUluZiwgc29ydC5ieT0iUFZhbHVlIiwgcC52YWx1ZT1hbHBoYV9sZXZlbCkpCnNpZ19taVJzID0gbGlzdCgpCmZvciAobG9nRkNfdGhyZXNob2xkIGluIDE6MikgewogIHNpZ19taVJzW1tsb2dGQ190aHJlc2hvbGRdXSA8LSByb3duYW1lcyhyZXN1bHRzW3Jlc3VsdHMkUFZhbHVlIDwgYWxwaGFfbGV2ZWwgJiByZXN1bHRzJEZEUiA8IGFscGhhX2xldmVsICYgYWJzKHJlc3VsdHMkbG9nRkMpID4gbG9nRkNfdGhyZXNob2xkLF0pICAjIGZpbHRlciBieSBwLXZhbHVlIGFuZCBsb2dGQwp9CnNpZ19taVJzW1szXV0gPC0gcm93bmFtZXMocmVzdWx0c1tyZXN1bHRzJFBWYWx1ZSA8IGFscGhhX2xldmVsICYgcmVzdWx0cyRGRFIgPCBhbHBoYV9sZXZlbCxdWzA6NTAsXSkKcmVzdWx0c19zb3J0ZWRfZmRyIDwtIHJlc3VsdHNbb3JkZXIocmVzdWx0cyRGRFIpLF0Kc2lnX21pUnNbWzRdXSA8LSByb3duYW1lcyhyZXN1bHRzX3NvcnRlZF9mZHJbcmVzdWx0c19zb3J0ZWRfZmRyJEZEUiA8IGFscGhhX2xldmVsLF1bMDo1MCxdKQojIGhlYXRtYXBzIG9mIHNpZ25pZmljYW50IERFIG1pUk5BcwpTb3VyY2UgPC0gcERhdGEocnV2cjIpWyxjKCJTb3VyY2UiKV0KYW5ub3RhdGlvbnMgPC0gYXMuZGF0YS5mcmFtZShTb3VyY2UpCnJvd25hbWVzKGFubm90YXRpb25zKSA8LSByb3duYW1lcyhwRGF0YShydXZyMikpCmZvcihtaVJfbGlzdCBpbiBzaWdfbWlScykgewogIHBoZWF0bWFwKG5vcm0uZXhwci5tYXRyW21pUl9saXN0LF0sIGNsdXN0ZXJfcm93cz1UUlVFLCBzaG93X3Jvd25hbWVzPVRSVUUsIGNsdXN0ZXJfY29scz1UUlVFLCBhbm5vdGF0aW9uX2NvbD1hbm5vdGF0aW9ucywgbWFpbj0iRGlmZmVyZW50aWFsbHkgRXhwcmVzc2VkIG1pUk5BcyIpCn0KYGBgCiMgREUgQW5hbHlzaXMgd2l0aCBERVNlcTIKYGBge3J9CmxpYnJhcnkoREVTZXEyKQojIHVzZSBmaWx0ZXJlZCBhbmQgUlVWci1jb3JyZWN0ZWQgZGF0YQpkZXNlcTJEYXRhIDwtIERFU2VxRGF0YVNldEZyb21NYXRyaXgoY291bnREYXRhID0gZGdlJGNvdW50cywgZGVzaWduID0gfiBQYXJ0aWNpcGFudC5JRCArIFNvdXJjZSArIFdfMSArIFdfMiwgY29sRGF0YSA9IHBEYXRhKHJ1dnIyKSkKZGVzZXEyRGF0YSA8LSBERVNlcShkZXNlcTJEYXRhKQpkZXNlcV9yZXN1bHRzIDwtIHJlc3VsdHMoZGVzZXEyRGF0YSwgY29va3NDdXRvZmY9RkFMU0UsIGluZGVwZW5kZW50RmlsdGVyaW5nPUZBTFNFKQp2c3REYXRhIDwtIHZhcmlhbmNlU3RhYmlsaXppbmdUcmFuc2Zvcm1hdGlvbihkZXNlcTJEYXRhLCBibGluZD1GQUxTRSkgICMgdHJhbnNmb3JtIHRoZSBkYXRhCmBgYAoKIyMgQ3JlYXRlIGEgaGVhdG1hcCBvZiB0aGUgdmFyaWFuY2Utc3RhYmlsaXppbmctdHJhbnNmb3JtZWQgZXhwcmVzc2lvbiBkYXRhCmBgYHtyfQogICAjIFRPRE86IG9ubHkgdXNlIHRvcCBERSBtaVJOQXMKdnN0RGF0YSA8LSB2YXJpYW5jZVN0YWJpbGl6aW5nVHJhbnNmb3JtYXRpb24oZGVzZXEyRGF0YSwgYmxpbmQ9RkFMU0UpICAjIHRyYW5zZm9ybSB0aGUgZGF0YQpzZWxlY3QgPC0gb3JkZXIocm93TWVhbnMoY291bnRzKGRlc2VxMkRhdGEsbm9ybWFsaXplZD1UUlVFKSksIGRlY3JlYXNpbmc9VFJVRSkKU291cmNlIDwtIGNvbERhdGEoZGVzZXEyRGF0YSlbLGMoIlNvdXJjZSIpXQphbm5vdGF0aW9ucyA8LSBhcy5kYXRhLmZyYW1lKFNvdXJjZSkKcm93bmFtZXMoYW5ub3RhdGlvbnMpIDwtIGNvbG5hbWVzKGRlc2VxMkRhdGEpCnBoZWF0bWFwKGFzc2F5KHZzdERhdGEpW3NlbGVjdCxdLCBjbHVzdGVyX3Jvd3M9VFJVRSwgc2hvd19yb3duYW1lcz1GQUxTRSwgY2x1c3Rlcl9jb2xzPVRSVUUsIGFubm90YXRpb25fY29sPWFubm90YXRpb25zKQpgYGAKCiMjIENyZWF0ZSBhIGhlYXRtYXAgb2Ygc2FtcGxlLXRvLXNhbXBsZSBkaXN0YW5jZXMKYGBge3J9CnNhbXBsZURpc3RzIDwtIGRpc3QodChhc3NheSh2c3REYXRhKSkpICAjIGNvbXB1dGUgc2FtcGxlLXRvLXNhbXBsZSBkaXN0YW5jZXMKc2FtcGxlRGlzdE1hdHJpeCA8LSBhcy5tYXRyaXgoc2FtcGxlRGlzdHMpCnJvd25hbWVzKHNhbXBsZURpc3RNYXRyaXgpIDwtIHBhc3RlKHZzdERhdGEkUGFydGljaXBhbnQuSUQsIHZzdERhdGEkU291cmNlLCBzZXA9IiAtICIpCmNvbG5hbWVzKHNhbXBsZURpc3RNYXRyaXgpIDwtIE5VTEwKY29sb3JzIDwtIGNvbG9yUmFtcFBhbGV0dGUoIHJldihicmV3ZXIucGFsKDksICJCbHVlcyIpKSApKDI1NSkKcGhlYXRtYXAoc2FtcGxlRGlzdE1hdHJpeCwKICAgICAgICAgY2x1c3RlcmluZ19kaXN0YW5jZV9yb3dzPXNhbXBsZURpc3RzLAogICAgICAgICBjbHVzdGVyaW5nX2Rpc3RhbmNlX2NvbHM9c2FtcGxlRGlzdHMsCiAgICAgICAgIGNvbD1jb2xvcnMpCmBgYAoKIyMgQ3JlYXRlIGEgUENBIHBsb3Qgc2hvd2luZyBBZ2UgeCBHZW5kZXIKYGBge3J9CnBsb3RQQ0EoCiAgICAgICAgICAgIHJlYWRzX3NjZSwKICAgICAgICAgICAgY29sb3VyX2J5ID0gIkFnZSIsCiAgICAgICAgICAgIHNoYXBlX2J5ID0gIkdlbmRlciIsCiAgICAgICAgICAgIHNpemVfYnkgPSAidG90YWxfZmVhdHVyZXMiLAogICAgICAgICAgICBleHByc192YWx1ZXMgPSAiUlVWciBrPSAyIgopICsgZ2d0aXRsZSgiUlVWU2VxLU5vcm1hbGl6ZWQgRXhwcmVzc2lvbiAoaz0yKSIpIApgYGAKCmBgYHtyfQpzYXZlLmltYWdlKCJkaWZmX2V4cHJfbWF0Y2hlZF9wbGFzbWFfdnNfc2VydW0uUkRhdGEiKQpgYGAKCg==